/*
 * @Author: fuhongliang-TSJ hongliang.fu@4d-bios.com
 * @Date: 2025-09-24 16:14:19
 * @LastEditTime: 2025-09-30 10:45:43
 * @LastEditors: fuhongliang-TSJ hongliang.fu@4d-bios.com
 * @Description: 
 * @FilePath: \lc_-esp32_-s3\main\app\he_feng_weather.c
 * 立创实战派ESP32-S3开发板
 * Copyright (c) 2025 by 4D Bios, All Rights Reserved. 
 */

#include "he_feng_weather.h"
#include <string.h>
#include <sys/param.h>
#include <stdlib.h>
#include <ctype.h>
#include "esp_log.h"
#include "esp_tls.h"
#include "esp_crt_bundle.h"
#include "esp_http_client.h"
#include "cJSON.h"
#include "zlib.h"

static const char *TAG = "he_feng_weather";

qweather_data_t st_qweather_data = {0}; //天气数据

// 和风天气配置
#define QWEATHER_API_KEY     "6996f6205c184a45869e65511a274782" // 和风天气的密钥KEY
#define QWEATHER_LOCATION_ID "101210101" // 杭州的城市代码 
#define HANG_ZHOU_LATITUDE   "30.16"     // 杭州的纬度
#define HANG_ZHOU_LONGITUDE  "120.10"    // 杭州的经度
#define QWEATHER_DAILY_URL "https://devapi.qweather.com/v7/weather/3d?location=" QWEATHER_LOCATION_ID "&key=" QWEATHER_API_KEY
#define QWEATHER_NOW_URL   "https://devapi.qweather.com/v7/weather/now?location=" QWEATHER_LOCATION_ID "&key=" QWEATHER_API_KEY
#define QAIR_NOW_URL       "https://devapi.qweather.com/v7/air/now?location=" QWEATHER_LOCATION_ID "&key=" QWEATHER_API_KEY
#define QAIR_NOW_NEW_URL   "https://devapi.qweather.com/airquality/v1/current/30.16/120.10"

#define MAX_HTTP_RECV_BUFFER 512
#define MAX_HTTP_OUTPUT_BUFFER 2048

/**
 * @description: HTTP回调函数
 * @param {esp_http_client_event_t} *evt
 * @return {*}
 */
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
    static char *output_buffer;  // Buffer to store response of http request from event handler
    static int output_len;       // Stores number of bytes read
    switch(evt->event_id) {
        case HTTP_EVENT_ERROR:
            ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
            break;
        case HTTP_EVENT_HEADER_SENT:
            ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
            break;
        case HTTP_EVENT_ON_HEADER:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
            break;
        case HTTP_EVENT_ON_DATA:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            /*
             *  Check for chunked encoding is added as the URL for chunked encoding used in this example returns binary data.
             *  However, event handler can also be used in case chunked encoding is used.
             */
            if (!esp_http_client_is_chunked_response(evt->client)) {
                // If user_data buffer is configured, copy the response into the buffer
                int copy_len = 0;
                if (evt->user_data) {
                    copy_len = MIN(evt->data_len, (MAX_HTTP_OUTPUT_BUFFER - output_len));
                    if (copy_len) {
                        memcpy(evt->user_data + output_len, evt->data, copy_len);
                    }
                } else {
                    const int buffer_len = esp_http_client_get_content_length(evt->client);
                    if (output_buffer == NULL) {
                        output_buffer = (char *) malloc(buffer_len);
                        output_len = 0;
                        if (output_buffer == NULL) {
                            ESP_LOGE(TAG, "Failed to allocate memory for output buffer");
                            return ESP_FAIL;
                        }
                    }
                    copy_len = MIN(evt->data_len, (buffer_len - output_len));
                    if (copy_len) {
                        memcpy(output_buffer + output_len, evt->data, copy_len);
                    }
                }
                output_len += copy_len;
            }

            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
            if (output_buffer != NULL) {
                // Response is accumulated in output_buffer. Uncomment the below line to print the accumulated response
                // ESP_LOG_BUFFER_HEX(TAG, output_buffer, output_len);
                free(output_buffer);
                output_buffer = NULL;
            }
            output_len = 0;
            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
            int mbedtls_err = 0;
            esp_err_t err = esp_tls_get_and_clear_last_error((esp_tls_error_handle_t)evt->data, &mbedtls_err, NULL);
            if (err != 0) {
                ESP_LOGI(TAG, "Last esp error code: 0x%x", err);
                ESP_LOGI(TAG, "Last mbedtls failure: 0x%x", mbedtls_err);
            }
            if (output_buffer != NULL) {
                free(output_buffer);
                output_buffer = NULL;
            }
            output_len = 0;
            break;
        case HTTP_EVENT_REDIRECT:
            ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
            esp_http_client_set_header(evt->client, "From", "user@example.com");
            esp_http_client_set_header(evt->client, "Accept", "text/html");
            esp_http_client_set_redirection(evt->client);
            break;
    }
    return ESP_OK;
}

/**
 * @description: GZIP解压函数
 * @param {char} *src
 * @param {int} srcLen
 * @param {char} *dst
 * @param {int*} dstLen
 * @return {*}
 */
static int gzDecompress(char *src, int srcLen, char *dst, int* dstLen)
{
    z_stream strm;
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;

    strm.avail_in = srcLen;
    strm.avail_out = *dstLen;
    strm.next_in = (Bytef *)src;
    strm.next_out = (Bytef *)dst;

    int err = -1;
    err = inflateInit2(&strm, 31); // 初始化
    if (err == Z_OK)
    {
        printf("inflateInit2 err=Z_OK\n");
        err = inflate(&strm, Z_FINISH); // 解压gzip数据
        if (err == Z_STREAM_END) // 解压成功
        { 
            printf("inflate err=Z_OK\n");
            *dstLen = strm.total_out; 
        } 
        else // 解压失败
        {
            printf("inflate err=!Z_OK\n");
        }
        inflateEnd(&strm);
    } 
    else
    {
        printf("inflateInit2 err! err=%d\n", err);
    }

    return err;
}

/**
 * @description: gzip解压第二种方法
 * @param {char} *compressed_data
 * @param {size_t} comp_len
 * @param {size_t} *decomp_len
 * @return {*}
 */
static char* gzip_decompress(const char *compressed_data, size_t comp_len, size_t *decomp_len) {
    z_stream strm = {0};
    char *decompressed_data = malloc(comp_len * 5); // 分配足够大的缓冲区
    if (!decompressed_data) return NULL;

    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = comp_len;
    strm.next_in = (Bytef *)compressed_data;
    strm.avail_out = comp_len * 5;
    strm.next_out = (Bytef *)decompressed_data;

    // 使用inflateInit2处理Gzip头
    int ret = inflateInit2(&strm, 16 + MAX_WBITS);
    if (ret != Z_OK) {
        free(decompressed_data);
        return NULL;
    }

    ret = inflate(&strm, Z_FINISH);
    inflateEnd(&strm);

    if (ret == Z_STREAM_END) {
        *decomp_len = strm.total_out;
        return decompressed_data;
    } else {
        free(decompressed_data);
        return NULL;
    }
}

/**
 * @description: gzip解压第三种方法
 * @param {void} *in_buf
 * @param {size_t} in_size
 * @param {void} *out_buf
 * @param {size_t} *out_size
 * @param {size_t} out_buf_size
 * @return {*}
 */
static int network_gzip_decompress(void *in_buf, size_t in_size, void *out_buf, size_t *out_size, size_t out_buf_size) {
        z_stream d_stream = {0};
        d_stream.zalloc = Z_NULL;
        d_stream.zfree = Z_NULL;
        d_stream.opaque = Z_NULL;
        d_stream.next_in = (Bytef *)in_buf;
        d_stream.avail_in = (uInt)in_size;
        d_stream.next_out = (Bytef *)out_buf;
        d_stream.avail_out = (uInt)out_buf_size;
  
        // 使用 inflateInit2 处理 gzip 头（15位窗口 + 32开启gzip解码）
        int ret = inflateInit2(&d_stream, 15 + 32);
        if (ret != Z_OK) {
        return ret;
    }  
  
    // 解压操作
    ret = inflate(&d_stream, Z_FINISH);
    if (ret != Z_STREAM_END) {
    inflateEnd(&d_stream);
    return ret == Z_OK ? Z_DATA_ERROR : ret;
    }
  
    *out_size = d_stream.total_out;
    inflateEnd(&d_stream);
    return Z_OK;
}

/**
 * @description: 解析当日天气JSON字符串
 * @param {char} *json_str
 * @return {*}
 */
static void parse_json_daily_weather(char *json_str)
{
    char *hc_response = NULL;

    cJSON *root = cJSON_Parse(json_str);
    if (root == NULL)
    {
        ESP_LOGI(TAG, "JSON-Parse Err: [%s]\n", cJSON_GetErrorPtr());
        return;
    }

    /*打印json字符串
    char *display = cJSON_PrintUnformatted(root);
    printf(display);
    free(display);
    */

    cJSON *code = cJSON_GetObjectItem(root, "code");
    
    hc_response = code->valuestring;
    ESP_LOGW(TAG, "HTTP-CLIENT Daily Weather Response: \"%s\"", hc_response);

    if (strcmp(hc_response, "200") == 0)
    {
        cJSON *daily_array = cJSON_GetObjectItem(root, "daily");
        if (cJSON_IsArray(daily_array))
        {
            cJSON *today = cJSON_GetArrayItem(daily_array, 0);
            if (today != NULL)  
            {
                char *temp_max = cJSON_GetObjectItem(today, "tempMax")->valuestring;
                char *temp_Min = cJSON_GetObjectItem(today, "tempMin")->valuestring;
                char *sun_rise = cJSON_GetObjectItem(today, "sunrise")->valuestring;
                char *sun_set = cJSON_GetObjectItem(today, "sunset")->valuestring;

                st_qweather_data.daily_temp_max = atoi(temp_max);
                st_qweather_data.daily_temp_min = atoi(temp_Min);
                memset(st_qweather_data.daily_sun_rise, 0, sizeof(st_qweather_data.daily_sun_rise));
                memset(st_qweather_data.daily_sun_set, 0, sizeof(st_qweather_data.daily_sun_set));
                strncpy(st_qweather_data.daily_sun_rise, sun_rise, DAILY_SUN_LEN);
                strncpy(st_qweather_data.daily_sun_set, sun_set, DAILY_SUN_LEN);

                ESP_LOGI(TAG, "最高气温：%d", st_qweather_data.daily_temp_max);
                ESP_LOGI(TAG, "最低气温：%d", st_qweather_data.daily_temp_min);
                ESP_LOGI(TAG, "日出时间：%s", st_qweather_data.daily_sun_rise);
                ESP_LOGI(TAG, "日落时间：%s", st_qweather_data.daily_sun_set);
            }
        }
    }

    cJSON_Delete(root);
}

/**
 * @description: 解析当前天气JSON字符串
 * @param {char} *json_str
 * @return {*}
 */
static void parse_json_now_weather(char *json_str)
{
    char *hc_response = NULL;

    cJSON *root = cJSON_Parse(json_str);
    if (root == NULL)
    {
        ESP_LOGI(TAG, "JSON-Parse Err: [%s]\n", cJSON_GetErrorPtr());
        return;
    }

    /*打印json字符串
    char *display = cJSON_PrintUnformatted(root);
    printf(display);
    free(display);
    */

    cJSON *code = cJSON_GetObjectItem(root, "code");
    
    hc_response = code->valuestring;
    ESP_LOGW(TAG, "HTTP-CLIENT Daily Weather Response: \"%s\"", hc_response);

    if (strcmp(hc_response, "200") == 0)
    {
        cJSON *now = cJSON_GetObjectItem(root, "now");
        if (cJSON_IsObject(now))
        {
            char *temp = cJSON_GetObjectItem(now, "temp")->valuestring;
            char *icon = cJSON_GetObjectItem(now, "icon")->valuestring;
            char *humidity = cJSON_GetObjectItem(now, "humidity")->valuestring;

            st_qweather_data.now_temp = atoi(temp);
            st_qweather_data.now_humi = atoi(humidity);
            st_qweather_data.now_icon = atoi(icon);

            ESP_LOGI(TAG, "温度：%d", st_qweather_data.now_temp);
            ESP_LOGI(TAG, "湿度：%d", st_qweather_data.now_humi);
            ESP_LOGI(TAG, "图标：%d", st_qweather_data.now_icon);
        }
    }

    cJSON_Delete(root);
}

/**
 * @description: 解析空气质量等级JSON字符串
 * @param {char} *json_str
 * @return {*}
 */
static void parse_json_air_quality(char *json_str)
{
    char *hc_response = NULL;

    cJSON *root = cJSON_Parse(json_str);
    if (root == NULL)
    {
        ESP_LOGI(TAG, "JSON-Parse Err: [%s]\n", cJSON_GetErrorPtr());
        return;
    }

    /*打印json字符串
    char *display = cJSON_PrintUnformatted(root);
    printf(display);
    free(display);
    */

    cJSON *code = cJSON_GetObjectItem(root, "code");
    
    hc_response = code->valuestring;
    ESP_LOGW(TAG, "HTTP-CLIENT Daily Weather Response: \"%s\"", hc_response);

    if (strcmp(hc_response, "200") == 0)
    {
        cJSON *now = cJSON_GetObjectItem(root, "now");
        if (cJSON_IsObject(now))
        {
            char *level = cJSON_GetObjectItem(now, "level")->valuestring;

            st_qweather_data.air_quality_level = atoi(level);

            ESP_LOGI(TAG, "空气质量：%d", st_qweather_data.air_quality_level);
        }
    }

    cJSON_Delete(root);
}

/**
 * @description: 解析通过新API获取的空气质量等级JSON字符串
 * @param {char} *json_str
 * @return {*}
 */
static void parse_json_air_quality_new_api(char *json_str)
{
    char *hc_response = NULL;

    cJSON *root = cJSON_Parse(json_str);
    if (root == NULL)
    {
        ESP_LOGI(TAG, "JSON-Parse Err: [%s]\n", cJSON_GetErrorPtr());
        return;
    }

    /*打印json字符串
    char *display = cJSON_PrintUnformatted(root);
    printf(display);
    free(display);
    */

    cJSON *indexes = cJSON_GetObjectItem(root, "indexes");

    if (cJSON_IsArray(indexes))
    {
        cJSON *index_0 = cJSON_GetArrayItem(indexes, 0);
        if (index_0 != NULL)  
        {
            char *level = cJSON_GetObjectItem(index_0, "level")->valuestring;

            st_qweather_data.air_quality_level = atoi(level);

            ESP_LOGI(TAG, "空气质量：%d", st_qweather_data.air_quality_level);
        }
    }

    cJSON_Delete(root);
}

// 获取每日天气预报
void get_daily_weather(void)
{
    int client_code = 0;
    int64_t gzip_len = 0;
    char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};

    esp_http_client_config_t config = {
        .url = QWEATHER_DAILY_URL,
        .event_handler = _http_event_handler,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .user_data = local_response_buffer,        // Pass address of local buffer to get response
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) 
    {
        client_code = esp_http_client_get_status_code(client);
        gzip_len = esp_http_client_get_content_length(client);
        ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %"PRIu64, client_code, gzip_len);
    } else {
        ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
    }

    if (client_code == 200)
    {
        int buffSize = gzip_len * 5;
        char* buffData = (char*)heap_caps_malloc(buffSize, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
        memset(buffData, 0, buffSize);

        int ret = gzDecompress(local_response_buffer, gzip_len, buffData, &buffSize);

        if (Z_STREAM_END == ret) { /* 解压成功 */
            printf("daily weather decompress success\n");
            printf("buffSize = %d\n", buffSize);
            parse_json_daily_weather(buffData);

            st_qweather_data.daily_update_flag = 1;
        }
        else {
            printf("decompress failed:%d\n", ret);
        }

        heap_caps_free(buffData);
    }
    
    esp_http_client_cleanup(client);
}

// 获取实时天气信息
void get_now_weather(void)
{
    int client_code = 0;
    int64_t gzip_len = 0;
    char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};

    esp_http_client_config_t config = {
        .url = QWEATHER_NOW_URL,
        .event_handler = _http_event_handler,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .user_data = local_response_buffer,        // Pass address of local buffer to get response
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) 
    {
        client_code = esp_http_client_get_status_code(client);
        gzip_len = esp_http_client_get_content_length(client);
        ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %"PRIu64, client_code, gzip_len);
    } else {
        ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
    }

    if (client_code == 200)
    {
        int buffSize = gzip_len * 5;
        char* buffData = (char*)heap_caps_malloc(buffSize, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
        memset(buffData, 0, buffSize);

        int ret = gzDecompress(local_response_buffer, gzip_len, buffData, &buffSize);

        if (Z_STREAM_END == ret) { /* 解压成功 */
            printf("now weather decompress success\n");
            printf("buffSize = %d\n", buffSize);
            parse_json_now_weather(buffData);

            st_qweather_data.now_update_flag = 1;
        }
        else {
            printf("decompress failed:%d\n", ret);
        }

        heap_caps_free(buffData);
    }
    
    esp_http_client_cleanup(client);
}

// 获取实时空气质量
void get_air_quality(void)
{
    int client_code = 0;
    int64_t gzip_len = 0;
    char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};

    esp_http_client_config_t config = {
        .url = QAIR_NOW_URL,
        .event_handler = _http_event_handler,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .user_data = local_response_buffer,        // Pass address of local buffer to get response
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) 
    {
        client_code = esp_http_client_get_status_code(client);
        gzip_len = esp_http_client_get_content_length(client);
        ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %"PRIu64, client_code, gzip_len);
    } else {
        ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
    }

    if (client_code == 200)
    {
        //这里返回的数据比较多，内存分配太小的话解压会出错
        int buffSize = gzip_len * 5;
        char* buffData = (char*)heap_caps_malloc(buffSize, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
        memset(buffData, 0, buffSize);

        int ret = gzDecompress(local_response_buffer, gzip_len, buffData, &buffSize);

        if (Z_STREAM_END == ret) { /* 解压成功 */
            printf("air quality decompress success\n");
            printf("buffSize = %d\n", buffSize);
            parse_json_air_quality(buffData);

            st_qweather_data.air_quality_update_flag = 1;
        }
        else {
            printf("decompress failed:%d\n", ret);
        }

        heap_caps_free(buffData);

        /* 第二种解压方法
        size_t decomp_len;
        char *decompressed = gzip_decompress(local_response_buffer, gzip_len, &decomp_len);
        if (decompressed) {
            printf("decompress success\n");
            printf("buffSize = %d\n", decomp_len);
            parse_json_air_quality(decompressed);
            free(decompressed);
        } else {
            ESP_LOGE(TAG, "Gzip decompression failed!");
        }
        */

        /* 第三种解压方法
        size_t expected_dec_size = gzip_len * 5;
        size_t buffSize = MAX_HTTP_OUTPUT_BUFFER;
        char* buffData = (char*)heap_caps_malloc(expected_dec_size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
        memset(buffData, 0, expected_dec_size);
        int zlib_ret = network_gzip_decompress(local_response_buffer, gzip_len,
                                        buffData, &buffSize,
                                        expected_dec_size);
        if (zlib_ret != Z_OK) {
            ESP_LOGE(TAG, "解压错误: 代码=%d", zlib_ret);
        }
        else
        {
            ESP_LOGI(TAG, "成功解压: 原始=%" PRIu64 " → 解压后=%d", gzip_len, buffSize);
            parse_json_air_quality(buffData);
        }
        heap_caps_free(buffData);
        */
    }
    
    esp_http_client_cleanup(client);
}

// 获取实时空气质量用新API接口
void get_air_quality_by_new_api(void)
{
    int client_code = 0;
    int64_t gzip_len = 0;
    char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};

    esp_http_client_config_t config = {
        .url = QAIR_NOW_NEW_URL,
        .event_handler = _http_event_handler,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .user_data = local_response_buffer,        // Pass address of local buffer to get response
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_http_client_set_header(client, "X-QW-Api-Key", QWEATHER_API_KEY);
    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) 
    {
        client_code = esp_http_client_get_status_code(client);
        gzip_len = esp_http_client_get_content_length(client);
        ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %"PRIu64, client_code, gzip_len);
    } else {
        ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
    }

    if (client_code == 200)
    {
        //这里返回的数据比较多，内存分配太小的话解压会出错
        int buffSize = gzip_len * 5;
        char* buffData = (char*)heap_caps_malloc(buffSize, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
        memset(buffData, 0, buffSize);

        int ret = gzDecompress(local_response_buffer, gzip_len, buffData, &buffSize);

        if (Z_STREAM_END == ret) { /* 解压成功 */
            printf("air quality new api decompress success\n");
            printf("buffSize = %d\n", buffSize);
            parse_json_air_quality_new_api(buffData);

            st_qweather_data.air_quality_update_flag = 1;
        }
        else {
            printf("decompress failed:%d\n", ret);
        }

        heap_caps_free(buffData);
    }
    
    esp_http_client_cleanup(client);
}